package net.entelijan.cobean.text;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.AbstractButton;
import javax.swing.text.JTextComponent;

import net.entelijan.cobean.core.ICobean;
import net.entelijan.cobean.core.IInitializer;
import net.entelijan.cobean.core.impl.AbstractBoundPropertyValueComponentBean;
import net.entelijan.cobean.util.CobeanUtil;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * TODO More detailed documentation
 * 
 * @param <T>
 *            The class of the value represented by the input field. Can be any
 *            java object as long as you use an appropriate converter.
 */
public class DefaultTextInitializer<T> implements IInitializer<ICobean<T>> {

	private static Log log = LogFactory.getLog(DefaultTextInitializer.class);

	private JTextComponent coTextComponent;

	private JTextComponent coFailureComponent;

	private AbstractButton coResetButton;

	private ITextConverter<T> converter = createDefaultConverter();

	private IValidator<T> validator = new AlwaysOkValidator();

	private transient T latestValidValue = null;

	private transient Color initialBackgroundColor;

	private Color failureBackgroundColor;

	private ValidationEvent validationEvent = ValidationEvent.FOCUS_LOST;

	public static final Color COLOR_YELLOW = new Color(255, 255, 170);
	public static final Color COLOR_RED = new Color(255, 170, 170);
	public static final Color COLOR_PINK = new Color(255, 170, 255);
	public static final Color COLOR_BLUE = new Color(170, 170, 255);

	public DefaultTextInitializer() {
		super();
	}

	@Override
	public ICobean<T> initialize() {
		final TextCobean re = new TextCobean();

		if (validationEvent == ValidationEvent.FOCUS_LOST) {
			coTextComponent.addFocusListener(new FocusListener() {

				@Override
				public void focusLost(FocusEvent evt) {
					handleEvent(re);
				}

				@Override
				public void focusGained(FocusEvent e) {
					// Nothing to do
				}
			});

		} else if (validationEvent == ValidationEvent.KEY_RELEASED) {
			coTextComponent.addKeyListener(new KeyListener() {

				@Override
				public void keyTyped(KeyEvent e) {
					// Nothing to do
				}

				@Override
				public void keyReleased(KeyEvent e) {
					handleEvent(re);
				}

				@Override
				public void keyPressed(KeyEvent e) {
					// Nothing to do
				}
			});
		}

		if (this.coResetButton != null) {
			this.coResetButton.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					re.reset();
				}
			});
		}

		return re;
	}

	private void handleEvent(final TextCobean re) {
		final String val = coTextComponent.getText();
		try {
			T convValue = converter.convertToObject(val);
			IValidationResult resu = validator.validate(convValue);
			if (resu.ok()) {
				showFailure("");
				re.setValue(convValue);
				latestValidValue = convValue;
			} else {
				showFailure(resu.getMessage());
			}
		} catch (IllegalTextException e) {
			if (log.isDebugEnabled()) {
				log.debug(
						"Could not convert '" + val + "' because: "
								+ e.getMessage(), e);
			}
			showFailure(e.getMessage());
		}
	}

	private void showFailure(String message) {
		if (coFailureComponent == null) {
			if (message != null && !"".equals(message))
				log.warn("Cannot show failuer '"
						+ message
						+ "' because no failure component 'coFailure' is defined");
		} else {
			String oldMessage = coFailureComponent.getText();
			coFailureComponent.setText(message);
			if (failureBackgroundColor != null) {
				if (message != null && message.length() > 0) {
					coTextComponent.setBackground(failureBackgroundColor);
				} else {
					coTextComponent.setBackground(initialBackgroundColor);
				}
			}
			// Notify focus listeners that the value has changed
			if (message != null) {
				if (!message.equals(oldMessage)) {
					for (FocusListener fl : coFailureComponent
							.getFocusListeners()) {
						FocusEvent fe = new FocusEvent(coFailureComponent, 0);
						fl.focusLost(fe);
					}
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private ITextConverter<T> createDefaultConverter() {
		return (ITextConverter<T>) CobeanUtil
				.createInstance("net.entelijan.cobean.text.converter.StringTextConverter");
	}

	private final class AlwaysOkValidator implements IValidator<T> {
		@Override
		public IValidationResult validate(T value) {
			return new IValidationResult() {

				@Override
				public String getMessage() {
					return null;
				}

				@Override
				public boolean ok() {
					return true;
				}

			};
		}
	}

	private final class TextCobean extends
			AbstractBoundPropertyValueComponentBean<T> {
		@Override
		protected T getValuePlain() {
			return latestValidValue;
		}

		@Override
		protected void setValuePlain(T value) {
			IValidationResult res = validator.validate(value);
			if (res.ok()) {
				String strVal = converter.convertToString(value);
				coTextComponent.setText(strVal);
				latestValidValue = value;
				showFailure("");
			} else {
				showFailure(res.getMessage());
			}
		}

		public void reset() {
			final String strVal = coTextComponent.getText();
			// Try if the current value can be converted. If not set the latest
			// valid value.
			try {
				T converted = converter.convertToObject(strVal);
				IValidationResult resu = validator.validate(converted);
				if (!resu.ok()) {
					setValue(latestValidValue);
					showFailure("");
				}
			} catch (IllegalTextException e) {
				setValue(latestValidValue);
				showFailure("");
			}

		}

		@Override
		public String toString() {
			return "Cobean[JTextComponent]";
		}
	}

	public JTextComponent getCoTextComponent() {
		return coTextComponent;
	}

	public void setCoTextComponent(JTextComponent coTextComponent) {
		this.coTextComponent = coTextComponent;
		this.initialBackgroundColor = coTextComponent.getBackground();
	}

	public ITextConverter<T> getConverter() {
		return converter;
	}

	public void setConverter(ITextConverter<T> converter) {
		this.converter = converter;
	}

	public JTextComponent getCoFailureComponent() {
		return coFailureComponent;
	}

	public void setCoFailureComponent(JTextComponent coFailureComponent) {
		this.coFailureComponent = coFailureComponent;
	}

	public AbstractButton getCoResetButton() {
		return coResetButton;
	}

	public void setCoResetButton(AbstractButton coResetButton) {
		this.coResetButton = coResetButton;
	}

	public IValidator<T> getValidator() {
		return validator;
	}

	public void setValidator(IValidator<T> validator) {
		this.validator = validator;
	}

	public Color getFailureBackgroundColor() {
		return failureBackgroundColor;
	}

	public void setFailureBackgroundColor(Color failureBackgroundColor) {
		this.failureBackgroundColor = failureBackgroundColor;
	}

	public ValidationEvent getValidationEvent() {
		return validationEvent;
	}

	public void setValidationEvent(ValidationEvent validationEvent) {
		this.validationEvent = validationEvent;
	}

}
